/*
 * Copyright 2022 André Claßen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.burnoutcrew.reorderable

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
/**
 * 创建一个可重排序的 LazyList 状态对象，用于支持列表项拖拽重排功能。
 *
 * @param onMove 当两个项目交换位置时调用。参数为起始和目标位置信息。
 * @param listState 原始的 LazyListState，默认通过 [rememberLazyListState] 记住。
 * @param canDragOver 判断是否允许将某个元素拖动到另一个元素之上，默认为 null 表示全部允许。
 * @param onDragStart 拖拽开始时回调，传入起始索引及触摸坐标，默认为 null。
 * @param onDragEnd 拖拽结束时回调，传入起始与结束索引，默认为 null。
 * @param maxScrollPerFrame 每帧最大滚动距离，默认为 20.dp。
 * @param dragCancelledAnimation 拖拽取消后的动画效果，默认使用弹簧动画。
 * @param key 用于标识状态变化的键值，默认为 null。
 * @return 返回一个 [ReorderableLazyListState] 实例。
 */
@Composable
fun rememberReorderableLazyListState(
    onMove: (ItemPosition, ItemPosition) -> Unit,
    listState: LazyListState = rememberLazyListState(),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    maxScrollPerFrame: Dp = 20.dp,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation(),
    key: Any? = null
): ReorderableLazyListState {
    // 将 dp 转换为像素单位的最大滚动量
    val maxScroll = with(LocalDensity.current) { maxScrollPerFrame.toPx() }
    // 获取协程作用域以供内部逻辑使用
    val scope = rememberCoroutineScope()
    // 构造并记住 ReorderableLazyListState 实例
    val state = remember(listState, key) {
        ReorderableLazyListState(
            listState,
            scope,
            maxScroll,
            onMove,
            canDragOver,
            onDragStart,
            onDragEnd,
            dragCancelledAnimation
        )
    }
    // 获取当前布局方向判断是否是 RTL（从右向左）
    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

    // 启动协程监听可见项变化，并触发一次空位移拖动更新
    LaunchedEffect(state) {
        state.visibleItemsChanged()
            .collect { state.onDrag(0, 0) }
    }

    // 启动协程处理自动滚动逻辑
    LaunchedEffect(state) {
        // 根据布局方向和滚动方向计算实际滚动方向系数
        var reverseDirection = !listState.layoutInfo.reverseLayout
        if (isRtl && listState.layoutInfo.orientation != Orientation.Vertical) {
            reverseDirection = !reverseDirection
        }
        val direction = if (reverseDirection) 1f else -1f
        // 不断接收滚动请求并执行滚动操作
        while (true) {
            val diff = state.scrollChannel.receive()
            listState.scrollBy(diff * direction)
        }
    }
    return state
}

/**
 * 可重排序的 LazyList 状态管理类，继承自通用的 [ReorderableState]，
 * 提供了针对 LazyList 的具体实现。
 *
 * @property listState 原始的 LazyListState 对象。
 * @property scope 协程作用域。
 * @property maxScrollPerFrame 每帧最大滚动距离（像素）。
 * @property onMove 元素移动事件处理器。
 * @property canDragOver 是否可以拖放到某一项上。
 * @property onDragStart 拖拽开始事件处理器。
 * @property onDragEnd 拖拽结束事件处理器。
 * @property dragCancelledAnimation 拖拽取消后使用的动画。
 */
class ReorderableLazyListState(
    val listState: LazyListState,
    scope: CoroutineScope,
    maxScrollPerFrame: Float,
    onMove: (fromIndex: ItemPosition, toIndex: ItemPosition) -> (Unit),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation()
) : ReorderableState<LazyListItemInfo>(
    scope,
    maxScrollPerFrame,
    onMove,
    canDragOver,
    onDragStart,
    onDragEnd,
    dragCancelledAnimation
) {
    /**
     * 判断当前滚动方向是否垂直。
     */
    override val isVerticalScroll: Boolean
        get() = listState.layoutInfo.orientation == Orientation.Vertical

    /**
     * 获取指定项左侧偏移量。
     */
    override val LazyListItemInfo.left: Int
        get() = when {
            isVerticalScroll -> 0
            listState.layoutInfo.reverseLayout -> listState.layoutInfo.viewportSize.width - offset - size
            else -> offset
        }

    /**
     * 获取指定项顶部偏移量。
     */
    override val LazyListItemInfo.top: Int
        get() = when {
            !isVerticalScroll -> 0
            listState.layoutInfo.reverseLayout -> listState.layoutInfo.viewportSize.height - offset - size
            else -> offset
        }

    /**
     * 获取指定项右侧偏移量。
     */
    override val LazyListItemInfo.right: Int
        get() = when {
            isVerticalScroll -> 0
            listState.layoutInfo.reverseLayout -> listState.layoutInfo.viewportSize.width - offset
            else -> offset + size
        }

    /**
     * 获取指定项底部偏移量。
     */
    override val LazyListItemInfo.bottom: Int
        get() = when {
            !isVerticalScroll -> 0
            listState.layoutInfo.reverseLayout -> listState.layoutInfo.viewportSize.height - offset
            else -> offset + size
        }

    /**
     * 获取指定项宽度。
     */
    override val LazyListItemInfo.width: Int
        get() = if (isVerticalScroll) 0 else size

    /**
     * 获取指定项高度。
     */
    override val LazyListItemInfo.height: Int
        get() = if (isVerticalScroll) size else 0

    /**
     * 获取指定项在列表中的索引。
     */
    override val LazyListItemInfo.itemIndex: Int
        get() = index

    /**
     * 获取指定项的唯一键。
     */
    override val LazyListItemInfo.itemKey: Any
        get() = key

    /**
     * 获取当前可见的所有项的信息列表。
     */
    override val visibleItemsInfo: List<LazyListItemInfo>
        get() = listState.layoutInfo.visibleItemsInfo

    /**
     * 获取视口起始偏移量。
     */
    override val viewportStartOffset: Int
        get() = listState.layoutInfo.viewportStartOffset

    /**
     * 获取视口结束偏移量。
     */
    override val viewportEndOffset: Int
        get() = listState.layoutInfo.viewportEndOffset

    /**
     * 获取第一个可见项的索引。
     */
    override val firstVisibleItemIndex: Int
        get() = listState.firstVisibleItemIndex

    /**
     * 获取第一个可见项的滚动偏移量。
     */
    override val firstVisibleItemScrollOffset: Int
        get() = listState.firstVisibleItemScrollOffset

    /**
     * 滚动到指定索引的项。
     *
     * @param index 目标项索引。
     * @param offset 额外偏移量。
     */
    override suspend fun scrollToItem(index: Int, offset: Int) {
        listState.scrollToItem(index, offset)
    }

    /**
     * 处理拖拽开始事件。
     *
     * @param offsetX X 方向偏移。
     * @param offsetY Y 方向偏移。
     * @return 是否成功启动拖拽。
     */
    override fun onDragStart(offsetX: Int, offsetY: Int): Boolean =
        if (isVerticalScroll) {
            super.onDragStart(0, offsetY)
        } else {
            super.onDragStart(offsetX, 0)
        }

    /**
     * 查找目标位置附近的可放置区域。
     *
     * @param x 当前 X 坐标。
     * @param y 当前 Y 坐标。
     * @param selected 正在被拖动的项。
     * @return 所有潜在的目标项集合。
     */
    override fun findTargets(x: Int, y: Int, selected: LazyListItemInfo) =
        if (isVerticalScroll) {
            super.findTargets(0, y, selected)
        } else {
            super.findTargets(x, 0, selected)
        }

    /**
     * 在多个候选目标中选择最终要放置的位置。
     *
     * @param draggedItemInfo 被拖动的项信息。
     * @param items 所有候选目标项。
     * @param curX 当前 X 坐标。
     * @param curY 当前 Y 坐标。
     * @return 最终选中的目标项。
     */
    override fun chooseDropItem(
        draggedItemInfo: LazyListItemInfo?,
        items: List<LazyListItemInfo>,
        curX: Int,
        curY: Int
    ) =
        if (isVerticalScroll) {
            super.chooseDropItem(draggedItemInfo, items, 0, curY)
        } else {
            super.chooseDropItem(draggedItemInfo, items, curX, 0)
        }
}
